<?php
/**
 * Query builder implementation
 *
 * @author André Silva
 */
class Criteria {
	private $annotation = array();

	private $class;
	private $table;
	private $primaryKey;
	private $select;
	private $offset;
	private $limit;
	private $where;
	private $order;
	private $group;
	private $joins = array();

	/**
	 * Create a new criteria instance for a class
	 * @param string $className
	 */
	function __construct($className) {
		$this->annotation = Annotation::forClass($className);
		if (!isset($this->annotation['annotation']) || !isset ($this->annotation['annotation']['Entity'])) {
			throw new Exception('Criteria error: '.$sql.'. '.$e->getMessage());
		}
		$this->class = $className;
		$this->table = $this->annotation['annotation']['Entity']['table'];
		$this->primaryKey = $this->annotation['annotation']['Entity']['primaryKey'];
		$this->select = implode(',', array_keys($this->annotation['columns']));
	}

	public function find() {
		$sql = Connection::getSchema()->generateSelect($this);

		try {
			$cmd = Connection::getInstance()->prepare($sql);
			$cmd->setFetchMode(PDO::FETCH_CLASS, $this->class);
			$cmd->execute();

			return $cmd->fetchAll();
		}
		catch (PDOException $e) {
			throw new Exception('SQL Error : ['.$sql.']. '.$e->getMessage());
		}
	}

	/**
	 * Get a single
	 * @param string $value
	 * @return Model
	 */
	public function get($value = null) {
		$this->limit = 1;
		if (!empty ($value)) {
			$this->where($this->primaryKey.'='.$value);
		}

		$result = $this->find();
		if (empty ($result)) {
			return null;
		}
		return $result[0];
	}

	/**
	 * Query the total rows
	 * @param string $column
	 * @return int
	 */
	public function count($column = null) {
		if ($column === null) {
			$column = $this->primaryKey;
		}
		$this->select = 'count('.$column.') as total';

		$sql = Connection::getSchema()->generateSelect($this);

		try {
			$cmd = Connection::getInstance()->prepare($sql);
			if ($cmd->execute()) {
				return (int)$cmd->fetch(PDO::FETCH_OBJ)->total;
			} else {
				return 0;
			}
		}
		catch (PDOException $e) {
			throw new Exception('SQL Error : ['.$sql.']. '.$e->getMessage());
		}
	}

	/**
	 * Convert a value to a type
	 * @param mixed $value
	 * @param string $type
	 * @return mixed
	 */
	public static function formatToView($value, $type) {
		if($value === null || gettype($value) == 'object') {
			return $value;
		}

		switch ($type) {
			case 'integer':
				return (int)$value;
				break;
			case 'float':
				return (float)$value;
				break;
			case 'boolean':
				return (boolean)$value;
				break;
			case 'date':
				return Date::toDate($value);
				break;
			case 'datetime':
				return Date::toDateTime($value);
				break;
			default:
				return Text::decode($value);
				break;
		}
	}

	/**
	 * Return the SQL format a value to a type
	 * @param mixed $value
	 * @param string $type
	 * @return string
	 */
	public static function formatToSQL($value, $type) {
		if($value === null) {
			return null;
		}
		if(gettype($value) == 'object') {
			throw new Exception("Can't convert a object to SQL format");
		}

		switch ($type) {
			case 'integer':
			case 'float':
				return "$value";
				break;
			case 'boolean':
				return ($value) ? '1' : '0';
				break;
			case 'date':
				return "'".Date::format(Connection::getSchema()->getDateFormat(), $value)."'";
				break;
			case 'datetime':
				return "'".Date::format(Connection::getSchema()->getDateFormat().' %H:%M:%S', $value)."'";
				break;
			default:
				return "'".Text::encode($value)."'";
				break;
		}
	}

	/**
	 * Change the properties selected
	 * @return Criteria
	 */
	public function &select() {
		$args = func_get_args();
		$this->select = implode(',', $args);
		return $this;
	}

	/**
	 * Add a restriction in criteria
	 * @return Criteria
	 */
	public function &where() {
		$args = func_get_args();
		if (!empty ($this->where)) {
			$this->where .= ' AND ';
		}
		$this->where .= implode(' AND ', $args);
		return $this;
	}

	/**
	 * Add a order param
	 * @return Criteria
	 */
	public function &order() {
		$args = func_get_args();
		$this->order = implode(',', $args);
		return $this;
	}

	/**
	 * Add a group param
	 * @return Criteria
	 */
	public function &group() {
		$args = func_get_args();
		$this->group = implode(',', $args);
		return $this;
	}

	/**
	 * Change the max results
	 * @param int $param
	 * @return Criteria
	 */
	public function &limit($param) {
		$this->limit = (int)$param;
		return $this;
	}

	/**
	 * Change the results starting position
	 * @param int $param
	 * @return Criteria
	 */
	public function &offset($param) {
		$this->offset = (int)$param;
		return $this;
	}

	/**
	 * Add a relation for join
	 * @param array $relation
	 * @return Criteria
	 */
	public function &join($relation) {
		array_push($this->joins, $relation);
		return $this;
	}

	/**
	 * Get the classname
	 * @return string
	 */
	public function getClassname() {
		return $this->class;
	}

	/**
	 * Get the table name
	 * @return string
	 */
	public function getTable() {
		return $this->table;
	}

	/**
	 * Get the primary key name
	 * @return string
	 */
	public function getPrimaryKey() {
		return $this->primaryKey;
	}

	/**
	 * Get the selection list
	 * @return string
	 */
	public function getSelect() {
		return $this->select;
	}

	/**
	 * Get the results starting position
	 * @return int
	 */
	public function getOffset() {
		return $this->offset;
	}

	/**
	 * Get the max results
	 * @return int
	 */
	public function getLimit() {
		return $this->limit;
	}

	/**
	 * Get all restriction for the criteria
	 * @return string
	 */
	public function getWhere() {
		return $this->where;
	}

	/**
	 * Get the order criteria
	 * @return string
	 */
	public function getOrderBy() {
		return $this->order;
	}

	/**
	 * Get the group criteria
	 * @return string
	 */
	public function getGroupBy() {
		return $this->group;
	}

	/**
	 * Get the join list
	 * @return array
	 */
	public function getJoins() {
		return $this->joins;
	}

	/**
	 * Get the annotation information
	 * @return array
	 */
	public function getAnnotation() {
		return $this->annotation;
	}

	/**
	 * Get the class name
	 * @return string
	 */
	public function getClass() {
		return $this->class;
	}
}
?>
